"""
Reveal.js animation engine.

Implements animation rendering for Reveal.js presentations.
"""

from typing import Dict, List, Any, Optional
import json

from .base_engine import BaseAnimationEngine, RenderingError
from ...parser.content_types import AnimatedContent, ContentNode, Section, ContentType


class RevealJSEngine(BaseAnimationEngine):
    """Animation engine for Reveal.js presentations."""
    
    # Mapping from our animation types to Reveal.js/CSS classes
    ANIMATION_MAPPING = {
        'fadeIn': 'fade-in',
        'fadeOut': 'fade-out', 
        'slideIn': 'slide-in',
        'slideOut': 'slide-out',
        'appear': 'fragment fade-in',
        'zoom': 'fragment zoom-in',
        'bounce': 'fragment fade-in-then-out',
        'flip': 'fragment flip'
    }
    
    # Direction mapping for slide animations
    DIRECTION_MAPPING = {
        'left': 'slide-in-left',
        'right': 'slide-in-right',
        'up': 'slide-in-up',
        'down': 'slide-in-down'
    }
    
    def __init__(self, config: Optional[Dict[str, Any]] = None):
        """Initialize Reveal.js engine."""
        default_config = {
            'transition': 'slide',
            'transition_speed': 'default',
            'auto_animate': True,
            'fragment_in_url': True,
            'hash_one_based_index': False
        }
        
        if config:
            default_config.update(config)
        
        super().__init__(default_config)
    
    def render_animated_content(self, animated_content: AnimatedContent, 
                              context: Optional[Dict[str, Any]] = None) -> str:
        """Render animated content as HTML with Reveal.js classes."""
        content_html = self._render_content_node(animated_content.content)
        
        # Determine CSS classes for animation
        css_classes = self._get_animation_classes(animated_content)
        
        # Create data attributes for timing
        data_attrs = self._get_data_attributes(animated_content)
        
        # Wrap content with animation div
        return f'<div class="{css_classes}" {data_attrs}>{content_html}</div>'
    
    def render_section(self, section: Section, 
                      animations: List[AnimatedContent],
                      context: Optional[Dict[str, Any]] = None) -> str:
        """Render a complete section as Reveal.js slide."""
        slide_attrs = self._get_slide_attributes(section, animations)
        
        html_parts = [f'<section {slide_attrs}>']
        
        # Add section title
        if section.heading:
            html_parts.append(f'<h{section.level}>{section.heading}</h{section.level}>')
        
        # Group animations by timing for proper fragment ordering
        timeline = self.generate_animation_timeline(animations)
        
        # Render content in timeline order
        for timing_group in timeline['groups']:
            for animated_content in timing_group['animations']:
                rendered = self.render_animated_content(animated_content, context)
                html_parts.append(rendered)
        
        html_parts.append('</section>')
        return '\n'.join(html_parts)
    
    def generate_animation_timeline(self, animations: List[AnimatedContent]) -> Dict[str, Any]:
        """Generate timeline groups for Reveal.js fragments."""
        # Sort animations by delay, then by duration
        sorted_animations = sorted(animations, key=lambda x: (x.delay, x.duration))
        
        # Group animations with same or overlapping timing
        groups = []
        current_group = None
        
        for animation in sorted_animations:
            if (current_group is None or 
                animation.delay >= current_group['end_time']):
                # Start new group
                current_group = {
                    'start_time': animation.delay,
                    'end_time': animation.delay + animation.duration,
                    'animations': [animation]
                }
                groups.append(current_group)
            else:
                # Add to current group and extend end time if needed
                current_group['animations'].append(animation)
                current_group['end_time'] = max(
                    current_group['end_time'],
                    animation.delay + animation.duration
                )
        
        return {
            'total_duration': groups[-1]['end_time'] if groups else 0,
            'groups': groups,
            'fragment_count': len(groups)
        }
    
    def get_supported_features(self) -> List[str]:
        """Get supported Reveal.js features."""
        return [
            'fragments',
            'auto_animate', 
            'transitions',
            'slide_backgrounds',
            'speaker_notes',
            'pdf_export',
            'touch_navigation',
            'keyboard_shortcuts'
        ]
    
    def _render_content_node(self, content_node: ContentNode) -> str:
        """Render a content node to HTML."""
        if content_node.type == ContentType.TEXT:
            return f'<p>{content_node.data.text}</p>'
        
        elif content_node.type == ContentType.BULLET_LIST:
            items = [f'<li>{item}</li>' for item in content_node.data.items]
            items_html = "".join(items)
            return f'<ul>{items_html}</ul>'
        
        elif content_node.type == ContentType.IMAGE:
            alt_text = content_node.data.alt_text or ""
            title = content_node.data.title or ""
            return f'<img src="{content_node.data.path}" alt="{alt_text}" title="{title}">'
        
        elif content_node.type == ContentType.VIDEO:
            poster = f' poster="{content_node.data.poster_frame}"' if content_node.data.poster_frame else ""
            return f'<video controls{poster}><source src="{content_node.data.path}"></video>'
        
        elif content_node.type == ContentType.CODE_BLOCK:
            language = content_node.data.language or ""
            return f'<pre><code class="language-{language}">{content_node.data.code}</code></pre>'
        
        else:
            return f'<div>Unsupported content type: {content_node.type}</div>'
    
    def _get_animation_classes(self, animated_content: AnimatedContent) -> str:
        """Get CSS classes for animation."""
        classes = []
        
        # Base animation class
        animation_type = animated_content.animation_type
        if animation_type in self.ANIMATION_MAPPING:
            base_class = self.ANIMATION_MAPPING[animation_type]
            classes.append(base_class)
        
        # Direction-specific class for slide animations
        if (animation_type in ['slideIn', 'slideOut'] and 
            animated_content.direction and 
            animated_content.direction in self.DIRECTION_MAPPING):
            direction_class = self.DIRECTION_MAPPING[animated_content.direction]
            classes = [direction_class]  # Replace base class
        
        # Fragment class for sequential appearance
        if animated_content.delay > 0 or 'fragment' not in ' '.join(classes):
            if 'fragment' not in ' '.join(classes):
                classes.insert(0, 'fragment')
        
        return ' '.join(classes)
    
    def _get_data_attributes(self, animated_content: AnimatedContent) -> str:
        """Get data attributes for timing and effects."""
        attrs = []
        
        # Fragment index (for ordering)
        if animated_content.delay > 0:
            # Convert delay to fragment index (rough approximation)
            fragment_index = int(animated_content.delay * 10)  # 0.1s = index 1
            attrs.append(f'data-fragment-index="{fragment_index}"')
        
        # Animation duration (custom data attribute)
        if animated_content.duration != 1.0:
            attrs.append(f'data-animation-duration="{animated_content.duration}"')
        
        # Easing function
        if animated_content.easing != 'ease':
            attrs.append(f'data-animation-easing="{animated_content.easing}"')
        
        return ' '.join(attrs)
    
    def _get_slide_attributes(self, section: Section, 
                            animations: List[AnimatedContent]) -> str:
        """Get slide-level attributes."""
        attrs = []
        
        # Auto-animate if configured and animations present
        if self.config.get('auto_animate', True) and animations:
            attrs.append('data-auto-animate')
        
        # Transition override for this slide
        if hasattr(section, 'metadata') and section.metadata and 'transition' in section.metadata:
            transition = section.metadata['transition']
            attrs.append(f'data-transition="{transition}"')
        
        return ' '.join(attrs)
    
    def generate_reveal_config(self, presentation_config: Optional[Dict[str, Any]] = None) -> str:
        """Generate Reveal.js initialization configuration."""
        config = {
            'hash': True,
            'transition': self.config.get('transition', 'slide'),
            'transitionSpeed': self.config.get('transition_speed', 'default'),
            'autoAnimate': self.config.get('auto_animate', True),
            'fragmentInURL': self.config.get('fragment_in_url', True),
            'hashOneBasedIndex': self.config.get('hash_one_based_index', False),
            'controls': True,
            'progress': True,
            'center': True,
            'touch': True,
            'loop': False,
            'rtl': False,
            'keyboard': True,
            'overview': True,
            'margin': 0.1,
            'minScale': 0.2,
            'maxScale': 1.5
        }
        
        if presentation_config:
            config.update(presentation_config)
        
        return json.dumps(config, indent=2)